/*
 * // Bristleback plugin - Copyright (c) 2010 bristleback.googlecode.com
 * // ---------------------------------------------------------------------------
 * // This program is free software; you can redistribute it and/or modify it
 * // under the terms of the GNU Lesser General Public License as published by the
 * // Free Software Foundation; either version 3 of the License, or (at your
 * // option) any later version.
 * // This library is distributed in the hope that it will be useful,
 * // but without any warranty; without even the implied warranty of merchantability
 * // or fitness for a particular purpose.
 * // You should have received a copy of the GNU Lesser General Public License along
 * // with this program; if not, see <http://www.gnu.org/licenses/lgpl.html>.
 * // ---------------------------------------------------------------------------
 */

package pl.bristleback.server.bristle.utils;

import org.apache.log4j.Logger;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.jwebsocket.token.JSONToken;
import org.jwebsocket.token.Token;
import org.jwebsocket.token.TokenFactory;
import pl.bristleback.server.bristle.binding.BindingError;
import pl.bristleback.server.bristle.binding.BindingResult;
import pl.bristleback.server.bristle.config.BristleConstants;
import pl.bristleback.server.bristle.exceptions.BristleRuntimeException;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This utility class contains a huge amount of factory methods, used to create various types of token.
 * <p/>
 * Created on: 2010-11-28 13:51:16 <br/>
 *
 * @author Wojciech Niemiec
 */
public final class BristleTokenFactory {
  private static Logger log = Logger.getLogger(BristleTokenFactory.class.getName());

  private static final String REQUEST_ID_PARAMETER = "utid";
  private static final String RESPONSE_CODE = "code";
  private static final int RESPONSE_SUCCESS_CODE_VALUE = 0;
  public static final String VALIDATION_EXCEPTION_TYPE = "ValidationException";
  public static final String VALIDATION_EXCEPTION_ERRORS = "errors";
  public static final String VALIDATION_EXCEPTION_FIELD_NAME = "field";
  public static final String VALIDATION_EXCEPTION_ERROR_TYPE = "type";
  public static final String EXCEPTION_TYPE_PARAMETER = "_B_EX_T";
  public static final String EXCEPTION_VALUE_PARAMETER = "_B_EX_V";

  /**
   * Unsupported operation
   */
  private BristleTokenFactory() {
    throw new UnsupportedOperationException();
  }

  /**
   * Creates a json token with given json object inside and filled action name field.
   *
   * @param actionName action name
   * @param json       json object
   * @return json token.
   */
  public static JSONToken createJsonToken(String actionName, JSONObject json) {
    JSONToken jsonToken = new JSONToken(json);
    jsonToken.setNS(BristleConstants.BRISTLE_PLUGIN_NAMESPACE);
    jsonToken.setType(actionName);
    return jsonToken;
  }

  /**
   * Creates a Json token using given json array.
   * Because of Json token specification, array must be an element of parent json object.
   * Name of array is specified by user.
   *
   * @param actionName     action name
   * @param jsonArray      json array
   * @param arrayFieldName name of json array inside parent json object.
   * @return json token with given json array inside.
   */
  public static JSONToken createJsonToken(String actionName, JSONArray jsonArray, String arrayFieldName) {
    JSONObject json = new JSONObject();
    try {
      json.put(arrayFieldName, jsonArray);
      return createJsonToken(actionName, json);
    } catch (JSONException e) {
      throw new BristleRuntimeException("Problem while creating json token using json array, " + e.getMessage(), e.getCause());
    }
  }

  /**
   * Creates a {@link org.jwebsocket.token.MapToken} token with filled action name field.
   *
   * @param actionName action name.
   * @return token with filled namespace and action name field.
   */
  public static Token createToken(String actionName) {
    return TokenFactory.createToken(BristleConstants.BRISTLE_PLUGIN_NAMESPACE, actionName);
  }

  /**
   * Creates a {@link org.jwebsocket.token.JSONToken} token with filled action name field.
   *
   * @param actionName action name.
   * @return json token with filled namespace and action name field.
   */
  public static JSONToken createJsonToken(String actionName) {
    return new JSONToken(BristleConstants.BRISTLE_PLUGIN_NAMESPACE, actionName);
  }

  /**
   * Creates a response {@link org.jwebsocket.token.MapToken} token, copying action name and request identification.
   *
   * @param request request token containing request identification and requested action name.
   * @return a complete response token.
   */
  public static Token createResponseToken(Token request) {
    String actionName = request.getType();
    Token response = createToken(actionName);
    response.setInteger(REQUEST_ID_PARAMETER, getRequestId(request));
    response.setInteger(RESPONSE_CODE, RESPONSE_SUCCESS_CODE_VALUE);
    return response;
  }

  /**
   * Creates a response {@link org.jwebsocket.token.JSONToken} token, copying action name and request identification.
   *
   * @param request request token containing request identification and requested action name.
   * @return a complete response json token.
   */
  public static JSONToken createJsonResponseToken(Token request) {
    String actionName = request.getType();
    JSONToken response = createJsonToken(actionName);
    response.setInteger(REQUEST_ID_PARAMETER, getRequestId(request));
    return response;
  }

  /**
   * Creates a response json token with content given in JSONObject parameter, copying action name and request identification.
   *
   * @param request request token containing request identification and requested action name.
   * @param json    content of the response token.
   * @return a complete response token with content given in JSONObject parameter.
   */
  public static JSONToken createJsonResponseToken(Token request, JSONObject json) {
    String actionName = request.getType();
    JSONToken response = createJsonToken(actionName, json);
    response.setInteger(REQUEST_ID_PARAMETER, getRequestId(request));
    return response;
  }

  /**
   * Creates a response json token with content given in JSONArray parameter, copying action name and request identification.
   * Content array is put under name given as last parameter.
   *
   * @param request        request token containing request identification and requested action name.
   * @param json           content array of the response token.
   * @param arrayFieldName name under which given array is put.
   * @return a complete response token with content given in JSONArray parameter.
   */
  public static JSONToken createJsonResponseToken(Token request, JSONArray json, String arrayFieldName) {
    String actionName = request.getType();
    JSONToken response = createJsonToken(actionName, json, arrayFieldName);
    response.setInteger(REQUEST_ID_PARAMETER, getRequestId(request));
    return response;
  }

  /**
   * Creates a response token with filled exception type field,
   * this token will be processed by client's callback <code>onFailure()</code> method.
   * This exception token contains no exception value (only type),
   * but it can be added manually by setting token variable with key {@link #EXCEPTION_VALUE_PARAMETER}.
   *
   * @param request       request token containing request identification and requested action name.
   * @param exceptionType type of exception to thrown.
   * @return a complete exception response token.
   */
  public static Token createExceptionToken(Token request, String exceptionType) {
    Token exceptionToken = createResponseToken(request);
    exceptionToken.setString(EXCEPTION_TYPE_PARAMETER, exceptionType);
    return exceptionToken;
  }

  /**
   * Creates a json exception response token with filled exception type and value field,
   * this token will be processed by client's callback <code>onFailure()</code> method.
   * Exception value parameter is passed in parameter.
   *
   * @param request        request token containing request identification and requested action name.
   * @param exceptionType  type of exception to thrown.
   * @param exceptionValue value of exception.
   * @return a complete json exception response token.
   */
  public static JSONToken createJsonExceptionToken(Token request, String exceptionType, JSONObject exceptionValue) {
    JSONToken exceptionToken = createJsonResponseToken(request, new JSONObject());
    exceptionToken.put(EXCEPTION_VALUE_PARAMETER, exceptionValue);
    exceptionToken.setString(EXCEPTION_TYPE_PARAMETER, exceptionType);
    return exceptionToken;
  }

  /**
   * Creates a json exception response token with filled exception type field,
   * this token will be processed by client's callback <code>onFailure()</code> method.
   * This exception token contains no exception value (only type),
   * but it can be added manually by setting token variable with key {@link #EXCEPTION_VALUE_PARAMETER}.
   *
   * @param request       request token containing request identification and requested action name.
   * @param exceptionType type of exception to thrown.
   * @return a complete json exception response token.
   */
  public static JSONToken createJsonExceptionToken(Token request, String exceptionType) {
    JSONToken exceptionToken = createJsonResponseToken(request);
    exceptionToken.setString(EXCEPTION_TYPE_PARAMETER, exceptionType);
    return exceptionToken;
  }

  /**
   * Creates a json response token with exception type "ValidationException".
   * Value of this exception is set using binding errors. If given binding result wrapper doesn't contain any binding errors,
   * runtime exception is thrown. Exception value has variable named "errors",
   * which contains list of binding errors descriptions.
   * Every error has two variables: "field" (complete path of field) and "type" (type of validation error).
   *
   * @param request request token containing request identification and requested action name.
   * @param result  binding operation result wrapper with not empty list of binding errors.
   * @return a complete json validation exception token, with type "ValidationException".
   */
  public static Token createValidationExceptionToken(Token request, BindingResult<?> result) {
    List<BindingError> errors = result.getErrors();
    if (errors == null || errors.isEmpty()) {
      throw new BristleRuntimeException("Binding result contains no errors.");
    }

    Token exceptionToken = createJsonExceptionToken(request, VALIDATION_EXCEPTION_TYPE);

    Map<String, JSONObject> validationErrorsMap = new HashMap<String, JSONObject>();
    for (BindingError error : errors) {
      Map<String, String> validationErrorMap = new HashMap<String, String>();
      validationErrorMap.put(VALIDATION_EXCEPTION_FIELD_NAME, error.getFieldName());
      validationErrorMap.put(VALIDATION_EXCEPTION_ERROR_TYPE, error.getErrorType().toString());
      JSONObject validationError = new JSONObject(validationErrorMap);
      validationErrorsMap.put(error.getFieldName(), validationError);
    }
    JSONObject validationErrors = new JSONObject(validationErrorsMap);
    Map<String, JSONObject> exceptionValue = new HashMap<String, JSONObject>();
    exceptionValue.put(VALIDATION_EXCEPTION_ERRORS, validationErrors);
    exceptionToken.setMap(EXCEPTION_VALUE_PARAMETER, exceptionValue);

    return exceptionToken;
  }

  private static Integer getRequestId(Token request) {
    return request.getInteger(REQUEST_ID_PARAMETER);
  }
}
